using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Html;
using System.Runtime.CompilerServices;
using System.Serialization;
using Awaiter;
using ThemeChain;
using jQueryApi;

namespace Nsb.Classes
{
    [IgnoreNamespace]
    public class FileBlobBase
    {
        private const string EncodeLzw64 = "VXLZ64";
        private const string FileExt = ".txt";
        public static readonly string RetrieveBlobStringAwName = Helpers.MethodName(Script.Literal("'{0}'", (Action<Await, string, string, Dictionary>) RetrieveBlobStringAw));
        public static readonly string OutPkgStringAwName = Helpers.MethodName(Script.Literal("'{0}'", (Action<Await, StoreParams>) OutPkgStringAw));
        public static string Password;
        public static int SvCnt;
        private static readonly Dictionary<string, int> ModTime = new Dictionary();
        private static readonly Dictionary<string, bool> Xfr = new Dictionary();

        private static string GetUrl
        {
            get { return Uri.Join(ThemeBase.AppUri, "/file.get.php"); }
        }

        private static string PutUrl
        {
            get { return Uri.Join(ThemeBase.AppUri, "/file.put.php"); }
        }

        private static string DelUrl
        {
            get { return Uri.Join(ThemeBase.AppUri, "/file.del.php"); }
        }

        private static string ApnUrl
        {
            get { return Uri.Join(ThemeBase.AppUri, "/file.app.php"); }
        }

        private static string DirUrl
        {
            get { return Uri.Join(ThemeBase.AppUri, "/file.dir.php"); }
        }

        private static string XstUrl
        {
            get { return Uri.Join(ThemeBase.AppUri, "/file.exists.php"); }
        }

        public static void RetrieveBlobAw(Await awp, string partitionKey, string rowKey)
        {
            new Await()
                .AddDl(delegate(Await aw) { RetrieveBlobStringAw(aw, partitionKey, rowKey); })
                .HandleDl(Exceptions.BlobLoadError, Await.RethrowAw)
                .AddDx(delegate(Await aw) { awp.Result = Unpackage((string) aw.Result); })
                .Commit(awp);
        }

        public static void StoreBlobAw(Await awp, string partitionKey, string rowKey, string rxKey, object blob)
        {
            new Await()
                .AddDl(delegate(Await aw) { StoreBlobStringAw(aw, new Dictionary("partitionKey", partitionKey, "rowKey", rowKey, "rxKey", rxKey, "content", Repackage(blob))); })
                .HandleDl(Exceptions.BlobSaveError, Await.RethrowAw)
                .AddDx(delegate(Await aw) { awp[rxKey] = aw[rxKey]; })
                .Commit(awp);
        }

        public static void GetBlobAw(Await awp, Dictionary data)
        {
            RetrieveBlobAw(awp, (string) data["PartitionKey"], (string) data["RowKey"]);
        }

        public static void PutBlobAw(Await awp, object blob, Dictionary data)
        {
            StoreBlobAw(awp, (string) data["PartitionKey"], (string) data["RowKey"], PkgBase.ResultRx, blob);
        }

        [AlternateSignature]
        public static extern void RetrieveBlobStringAw(Await awp, string partitionKey, string rowKey);

        public static void RetrieveBlobStringAw(Await awp, string partitionKey, string rowKey, Dictionary opts)
        {
            object fnargs = "FileBlobBase.RetrieveBlobStringAw";
            //Script.Literal("debugger");
            Debug.Assert(rowKey != null, "rowKey != null");
            string fullKey = FullKey(partitionKey, rowKey);
            if (Xfr[fullKey])
            {
                Window.SetTimeout(delegate { RetrieveBlobStringAw(awp, partitionKey, rowKey); }, 13);
                return;
            }
            //string rxKey = PkgBase.ResultRx;
            Dictionary payload = opts ?? new Dictionary();
            payload["password"] = Password;
            payload["path"] = partitionKey;
            payload["file"] = rowKey + ((rowKey ?? "").IndexOf(".") == -1 ? FileExt : "");
            string devPassword = (string) Storage.GetLocal("DevPassword");
            if (!String.IsNullOrEmpty(devPassword))
                payload["devPassword"] = devPassword;

            jQueryAjaxOptions options = new jQueryAjaxOptions();
            options.Url = GetUrl;
            options.Data = payload;
            options.DataType = "jsonp";
            options.Type = "GET";

            options.Success =
                delegate(object fbo, string textStatus, jQueryXmlHttpRequest request1)
                {
                    Xfr[fullKey] = false;
                    awp["Data"] = fbo;
                    string msg = "";
                    try
                    {
                        PkgResult pr = (PkgResult) fbo;
                        msg = pr.msg;
                        if ((bool) Script.Literal("{0} !== true", pr.result))
                        {
                            if ((bool) Script.Literal("{0} === true", pr.inactive))
                                jQuery.Document.Trigger("InactiveCredentials");
                            throw new Exception(Exceptions.BlobLoadError);
                        }
                        awp.Result = Decode(pr.content);
                        if (pr.exists)
                            ModTime[fullKey] = pr.lastChanged;
                    }
                    catch (Exception ex)
                    {
                        options.Error(request1, msg, ex);
                        return;
                    }
                    SvCnt--;
                    awp.Done();
                };

            options.Error =
                delegate(jQueryXmlHttpRequest request, string textStatus, Exception error)
                {
                    SvCnt--;
                    Xfr[fullKey] = false;
                    Exception ex = Exceptions.AjaxError(Exceptions.BlobLoadError, fullKey, request, textStatus, error, fnargs);
                    if (ErrorReloading(ex))
                        return;
                    awp.Handle(ex);
                };
            Xfr[fullKey] = true;
            SvCnt++;
            jQuery.Ajax(options);
        }

        public static void AppendBlobStringAw(Await awp, string partitionKey, string rowKey, string rxKey, string content)
        {
            OutPkgStringAw(awp, new Dictionary("partitionKey", partitionKey, "rowKey", rowKey, "rxKey", rxKey, "content", content, "url", ApnUrl, "append", true));
        }

        [AlternateSignature]
        public static extern void StoreBlobStringAw(Await awp, Dictionary prms);

        //new Dictionary("partitionKey", partitionKey, "rowKey", rowKey, "rxKey", rxKey, "content", content, "url", PutUrl, "append", false, "backup", false)
        public static void StoreBlobStringAw(Await awp, StoreParams prms)
        {
            prms.url = PutUrl;
            OutPkgStringAw(awp, prms);
        }

        [AlternateSignature]
        public static extern void OutPkgStringAw(Await awp, Dictionary prms);

        public static void OutPkgStringAw(Await awp, StoreParams pm)
        {
            object fnargs = "FileBlobBase.OutPkgStringAw";
            pm.partitionKey = pm.partitionKey ?? ".";
            pm.rowKey = pm.rowKey ?? "";
            pm.rxKey = pm.rxKey ?? PkgBase.ResultRx;
            pm.content = pm.content ?? "";
            pm.url = pm.url ?? PutUrl;
            pm.append = Script.Value(pm.append, false);
            pm.backup = Script.Value(pm.backup, false);

            Debug.Assert(pm.rowKey != null, "rowKey != null");
            string fullKey = FullKey(pm.partitionKey, pm.rowKey);
            if (Xfr[fullKey])
            {
                //Inform.Debug("Waiting on {0}", fullKey);
                Window.SetTimeout(delegate { OutPkgStringAw(awp, pm); }, 13);
                return;
            }
            //Inform.Debug("Sending out a package");
            Dictionary payload = new Dictionary();
            payload["password"] = Password;
            payload["createFile"] = "true";
            payload["createPath"] = "true";
            payload["path"] = pm.partitionKey;
            payload["file"] = pm.rowKey + ((pm.rowKey ?? "").IndexOf(".") == -1 ? FileExt : "");
            payload["priorTime"] = Script.Value(ModTime[FullKey(pm.partitionKey, pm.rowKey)], 0);
            payload["forceSave"] = pm.append;
            payload["createBackup"] = pm.backup;
            payload["backupName"] = pm.rowKey + "-" + (new Date()).GetTime() + FileExt;
            payload["content64"] = Encoder.Encode64(Encode(pm.content));
            string devPassword = (string) Storage.GetLocal("DevPassword");
            if (!String.IsNullOrEmpty(devPassword))
                payload["devPassword"] = devPassword;

            jQueryAjaxOptions options = new jQueryAjaxOptions();
            options.Url = pm.url;
            options.Data = payload;
            options.Type = "POST";

            options.Success =
                delegate(object fbo, string textStatus, jQueryXmlHttpRequest request1)
                {
                    SvCnt--;
                    string msg = null;
                    try
                    {
                        //Inform.Debug("OutPkgStringAw result = {0}", Json.Stringify(fbo));
                        jQuery.Document.Trigger("SaveSpinnerOff");
                        if ((bool) Type.GetField(fbo, "result") != true)
                        {
                            if ((bool) Type.GetField(fbo, "fileNewer"))
                            {
                                Inform.Warn("Trying to write to a newer file {0}", FullKey(pm.partitionKey, pm.rowKey));
                                throw new Exception(Exceptions.FileIsNewer);
                            }
                            msg = Script.IsNullOrUndefined(fbo) ? "" : (string) Type.GetField(fbo, "msg");
                            throw new Exception(Exceptions.BlobSaveError);
                        }
                        ModTime[FullKey(pm.partitionKey, pm.rowKey)] = (int) Type.GetField(fbo, "lastChanged");
                    }
                    catch (Exception ex)
                    {
                        Ask.Ok(String.Format("File write failed \n{0}", ex));
                        switch (ex.Message)
                        {
                            case Exceptions.FileIsNewer:
                                Xfr[fullKey] = false;
                                awp.Handle(ex);
                                return;
                        }
                        Inform.Warn("Not able to send pkg {0}, error={1}", FullKey(pm.partitionKey, pm.rowKey), Script.Value(msg, ex.ToString()));
                        Xfr[fullKey] = false;
                        awp.Handle(new Exception(Exceptions.BlobSaveError));
                        return;
                    }
                    Xfr[fullKey] = false;
                    awp[pm.rxKey] = true;
                    //Inform.Debug("Done sending a package");
                    awp.Done();
                };

            options.Error =
                delegate(jQueryXmlHttpRequest request, string textStatus, Exception error)
                {
                    SvCnt--;
                    Xfr[fullKey] = false;
                    jQuery.Document.Trigger("SaveSpinnerOff");
                    Exception ex = Exceptions.AjaxError(Exceptions.BlobLoadError, fullKey, request, textStatus, error, fnargs);
                    if (ErrorReloading(ex))
                        return;
                    awp.Handle(ex);
                };
            SvCnt++;
            Xfr[fullKey] = true;
            jQuery.Ajax(options);
            jQuery.Document.Trigger("SaveSpinnerOn");
        }

        /// <summary>
        /// Deletes the item on the server
        /// </summary>
        /// <param name="awp">The awp.</param>
        /// <param name="partitionKey">The partition key.</param>
        /// <param name="rowKey">The row key. If no extension then FileExt will be appended to it.</param>
        public static void DeleteItemAw(Await awp, string partitionKey, string rowKey)
        {
            object fnargs = "FileBlobBase.DeleteItemAw";
            string fullKey = FullKey(partitionKey, rowKey);
            rowKey = rowKey ?? "";
            Dictionary payload = new Dictionary();
            payload["password"] = Password;
            payload["path"] = partitionKey;
            payload["file"] = rowKey + ((rowKey ?? "").IndexOf(".") == -1 ? FileExt : "");
            string devPassword = (string) Storage.GetLocal("DevPassword");
            if (!String.IsNullOrEmpty(devPassword))
                payload["devPassword"] = devPassword;

            jQueryAjaxOptions options = new jQueryAjaxOptions();
            options.Url = DelUrl;
            options.Data = payload;
            options.DataType = "jsonp";
            options.Type = "GET";

            options.Success =
                delegate(object fbo, string textStatus, jQueryXmlHttpRequest request1)
                {
                    SvCnt--;
                    try
                    {
                        if (Script.IsNullOrUndefined(fbo) || (bool) Type.GetField(fbo, "result") != true)
                        {
                            throw new Exception(Exceptions.BlobLoadError + ":" + fullKey);
                        }
                    }
                    catch (Exception ex)
                    {
                        Inform.Trace("Unable to delete pkg {0}/{1}, exception={3}", partitionKey, rowKey, ex);
                        awp.Handle(new Exception(Exceptions.BlobLoadError + ":" + fullKey));
                        return;
                    }
                    awp.Done();
                };

            options.Error =
                delegate(jQueryXmlHttpRequest request, string textStatus, Exception error)
                {
                    SvCnt--;
                    Exception ex = Exceptions.AjaxError(Exceptions.BlobLoadError, fullKey, request, textStatus, error, fnargs);
                    if (ErrorReloading(ex))
                        return;
                    awp.Handle(ex);
                };
            SvCnt++;
            jQuery.Ajax(options);
        }

        [AlternateSignature]
        public static extern void EmptyFolderAw(Await awp, string path);

        public static void EmptyFolderAw(Await awp, string path, bool removeRoot)
        {
            object fnargs = "FileBlobBase.EmptyFolderAw";
            string fullKey = path;
            Dictionary payload = new Dictionary();
            payload["password"] = Password;
            payload["path"] = path;
            payload["file"] = "";
            payload["rmDir"] = removeRoot ? "true" : "false";
            string devPassword = (string) Storage.GetLocal("DevPassword");
            if (!String.IsNullOrEmpty(devPassword))
                payload["devPassword"] = devPassword;

            jQueryAjaxOptions options = new jQueryAjaxOptions();
            options.Url = DelUrl;
            options.Data = payload;
            options.DataType = "jsonp";
            options.Type = "GET";

            options.Success =
                delegate(object fbo, string textStatus, jQueryXmlHttpRequest request1)
                {
                    SvCnt--;
                    try
                    {
                        if (Script.IsNullOrUndefined(fbo) || (bool) Type.GetField(fbo, "result") != true)
                        {
                            throw new Exception(Exceptions.BlobLoadError + ":" + fullKey);
                        }
                    }
                    catch (Exception ex)
                    {
                        Inform.Trace("Unable to empty folder {0}, exception={1}", path, ex);
                        awp.Handle(new Exception(Exceptions.BlobLoadError + ":" + fullKey));
                        return;
                    }
                    awp.Done();
                };

            options.Error =
                delegate(jQueryXmlHttpRequest request, string textStatus, Exception error)
                {
                    SvCnt--;
                    Exception ex = Exceptions.AjaxError(Exceptions.BlobLoadError, fullKey, request, textStatus, error, fnargs);
                    if (ErrorReloading(ex))
                        return;
                    awp.Handle(ex);
                };
            SvCnt++;
            jQuery.Ajax(options);
        }

        public static void ExistsAw(Await awp, string path)
        {
            object fnargs = "FileBlobBase.ExistsAw";
            Dictionary payload = new Dictionary();
            payload["password"] = Password;
            payload["file"] = path;
            string devPassword = (string) Storage.GetLocal("DevPassword");
            if (!String.IsNullOrEmpty(devPassword))
                payload["devPassword"] = devPassword;

            jQueryAjaxOptions options = new jQueryAjaxOptions();
            options.Url = XstUrl;
            options.Data = payload;
            options.DataType = "json";
            options.Type = "GET";
            options.Cache = true;

            options.Success =
                delegate(object data, string textStatus, jQueryXmlHttpRequest request1)
                {
                    awp["Data"] = data;
                    try
                    {
                        PkgResult pr = (PkgResult) data;
                        awp.Result = pr.result;
                    }
                    catch (Exception ex)
                    {
                        options.Error(request1, textStatus, ex);
                        return;
                    }
                    SvCnt--;
                    awp.Done();
                };

            options.Error =
                delegate(jQueryXmlHttpRequest request, string textStatus, Exception error)
                {
                    SvCnt--;
                    Exception ex = Exceptions.AjaxError(Exceptions.BlobLoadError, path, request, textStatus, error, fnargs);
                    if (ErrorReloading(ex))
                        return;
                    awp.Handle(ex);
                };
            SvCnt++;
            jQuery.Ajax(options);
        }

        [AlternateSignature]
        public static extern void GetDirAw(Await awp, string partitionKey);

        public static void GetDirAw(Await awp, string partitionKey, bool imageSizes)
        {
            object fnargs = "FileBlobBase.GetDirAw";
            string fullKey = partitionKey;
            int spinnerTimer = Window.SetTimeout(delegate { Await.Trigger("Spinner", true); }, 1300);
            Dictionary payload = new Dictionary();
            payload["password"] = Password;
            payload["path"] = partitionKey;
            payload["imagesizes"] = imageSizes ? true : false;

            jQueryAjaxOptions options = new jQueryAjaxOptions();
            options.Url = DirUrl;
            options.Data = payload;
            options.DataType = "json";
            options.Type = "GET";

            options.Success =
                delegate(object data, string textStatus, jQueryXmlHttpRequest request1)
                {
                    SvCnt--;
                    Window.ClearTimeout(spinnerTimer);
                    DomElement.Trigger("Spinner", false);
                    try
                    {
                        awp.Result = data;
                        awp["success"] = ((PkgResult) data).result;
                    }
                    catch (Exception ex)
                    {
                        Inform.Trace("Unable to get file list {0}, exception={1}", partitionKey, ex);
                        awp.Handle(new Exception(Exceptions.BlobLoadError + ":" + fullKey));
                        return;
                    }
                    awp.Done();
                };

            options.Error =
                delegate(jQueryXmlHttpRequest request, string textStatus, Exception error)
                {
                    SvCnt--;
                    Window.ClearTimeout(spinnerTimer);
                    DomElement.Trigger("Spinner", false);
                    Exception ex = Exceptions.AjaxError(Exceptions.BlobLoadError, fullKey, request, textStatus, error, fnargs);
                    if (ErrorReloading(ex))
                        return;
                    awp.Handle(ex);
                };
            SvCnt++;
            jQuery.Ajax(options);
        }

        private static bool ErrorReloading(Exception ex)
        {
            if (ThemeBase.Reloading)
            {
                Storage.SetLocal("DevIOError", ex.Message);
                return true;
            }
            return false;
        }

        public static void ForceStoreBlob(string partitionKey, string rowKey)
        {
            ModTime[FullKey(partitionKey, rowKey)] = 0;
        }

        private static string FullKey(string partitionKey, string rowKey)
        {
            return partitionKey + "/" + rowKey;
        }

        private static string Repackage(object blob)
        {
            try
            {
                return Json.Stringify(blob);
            }
            catch (Exception ex)
            {
                Inform.Error("Error stringifying blob: {0}", ex);
            }
            return "";
        }

        private static object Unpackage(string s)
        {
            try
            {
                if (!String.IsNullOrEmpty(s))
                    return Json.Parse(s);
            }
            catch (Exception ex)
            {
                Inform.Error("Error parsing blob: {0}", ex);
            }
            return "";
        }

        public static string Encode(string s)
        {
            return s;
            //return EncodeLzw64 + Encoder.Encode64(Encoder.LzwEncode(s));
        }

        private static string Decode(string s)
        {
            // All prefexes must be 6 characters in length
            string pfx = Script.IsNullOrUndefined(s) ? "" : s.Substr(0, 6);
            switch (pfx)
            {
                case EncodeLzw64:
                    return Encoder.LzwDecode(Encoder.Decode64(s.Substr(6)));

                default:
                    return String.IsNullOrEmpty(s) ? "" : s;
            }
        }

        //
        // The following list functions append, store, and retrieve naked lists
        //

        public static string CleanListData(string txt)
        {
            int s = 0;
            int e = txt.Length;
            if (txt.CharCodeAt(0) == 65279) s++; // Remove BOM
            while (e > 0 && "\r\n\t,; ".IndexOf(txt.CharAt(e - 1)) != -1) e--;
            return txt.Substring(s, e);
        }

        public static string SimpleList(object list)
        {
            string nl = Json.Stringify(list);
            nl = nl.ReplaceRegex(DomElement.RegMe(@"/^\[(.*)\]$/"), "$1");
            return nl.Replace("},{", "},\r\n{");
        }

        /// <summary>
        /// Append a List item to a naked list
        /// </summary>
        /// <param name="awp">The Await</param>
        /// <param name="partitionKey">The partition key.</param>
        /// <param name="rowKey">The row key.</param>
        /// <param name="item">The item to append.</param>
        public static void AddListItemAw(Await awp, string partitionKey, string rowKey, object item)
        {
            AppendBlobStringAw(awp, partitionKey, rowKey, DomElement.ResultRx, Json.Stringify(item));
        }

        /// <summary>
        /// Load a naked List and return the list object in aw.Result.
        /// </summary>
        /// <param name="awp">The Await.</param>
        /// <param name="partitionKey">The partition key.</param>
        /// <param name="rowKey">The row key.</param>
        public static void LoadListAw(Await awp, string partitionKey, string rowKey)
        {
            new Await()
                .AddAw(RetrieveBlobStringAw, partitionKey, rowKey)
                .HandleDl(Exceptions.BlobLoadError, Await.RethrowAw)
                .AddDx(delegate(Await aw)
                       {
                           string content = CleanListData((string) aw.Result);
                           awp.Result = aw.Result != null ? Json.Parse("[" + content + "]") : null;
                       })
                .Commit(awp);
        }

        /// <summary>
        /// Loades and then saves a naked list doing a modifier function on the list in between.
        /// </summary>
        /// <param name="awp">The awp.</param>
        /// <param name="partitionKey">The partition key.</param>
        /// <param name="rowKey">The row key.</param>
        /// <param name="fn">List modifier function </param>
        public static void UpdateListAw(Await awp, string partitionKey, string rowKey, Action<object> fn)
        {
            new Await()
                .AddAw(LoadListAw, partitionKey, rowKey)
                .AddDx(delegate(Await aw) { fn(aw.Result); })
                .AddDl(delegate(Await aw) { StoreBlobStringAw(aw, new Dictionary("partitionKey", partitionKey, "rowKey", rowKey, "content", SimpleList(aw.Result))); })
                .Commit(awp);
        }
    }

    [IgnoreNamespace]
    public sealed class PkgResult
    {
        public string activateId;
        public string content;
        public bool devServer;
        public string email;
        public bool exists;
        public bool inactive;
        public int lastChanged;
        public Dictionary<string, string> log;
        public string msg;
        public bool result;
        public string userid;
    }

    [IgnoreNamespace]
    public sealed class StoreParams
    {
        public bool append;
        public bool backup;
        public string content;
        public string partitionKey;
        public string rowKey;
        public string rxKey;
        public string url;
    }

    [IgnoreNamespace]
    public sealed class DirMap
    {
        public int dircnt;
        public List<string> dirs;
        public string dripath;
        public int filecnt;
        public List<string> files;
        public List<string> imgsizes;
        public bool result;
        public List<int> sizes;
    }
}